import {isUndefined}  from './utils';
import {Types} from './types';
export class Token{
    label:string;
    type:number;
}
export class Tokenizer{
    private input:string;
    public index:number;
    private context:number;
    public eof:boolean;
    constructor( input:string){
        this.input=input;
        this.index=0;
        this.context=null;
        this.eof=false;
    }
    public nextToken():Token{
        this.eatSpaces();
        return( 
            this.readCloseTag() ||
            this.readTagName() ||
            this.readAttrName() ||
            this.readAttrEqual() ||
            this.readAttrString() ||
            this.readGT() ||
            this.readSlashGT() 
            ||this.readIf()
            ||this.readElse()
            ||this.readElseIf()
            ||this.readEndIf()
            ||this.readEach()
            ||this.readEndEach()
            ||this.readText()
            || this.readEOF() ||
            this.error()
        );
    }
    /**
 * 预览一个token，index不增加
 */
  public peekToken = function ():Token{
        var index = this.index;
        var token = this.nextToken();
        this.index = index;
        return token;
    }
    private char():string{
        return this.input[this.index];
    }
    private setContext(type:number):void{
        this.context=type
    }
    private inContext(type:number):boolean{
        return this.context===type;
    }
    private error():Token{
        throw new Error('Unexpected token: \'' + this.char() + '\'')
    }
    private eatSpaces(){
        while(/\s/.test(this.char())){
            this.index++;
        }
    }
    private test() {
        
    }
    private captureByRegx(regx:RegExp,type:number):{
        type:number;
        label:string;
    }{
        let tinput=this.input.slice(this.index);
        let capture=tinput.match(regx);
        if(capture){
             let tcapture=capture[0];
             this.index+=tcapture.length;
             this.setContext(type);
             return{
                 type:type,
                 label:tcapture
             }
        }
    }
    private readTagName():Token{
        if(this.char()==='<'){
            this.index++;
            this.eatSpaces();
            let start=this.index;
            while(this.char().match(/[\w\d]/)){
                this.index++;
            }
            let tagName=this.input.slice(start,this.index);
            this.setContext(Types.TK_TAG_NAME);
            return {
                type:Types.TK_TAG_NAME,
                label:tagName
            }
        }
    }
    private readAttrName():Token{
        if(this.inContext(Types.TK_TAG_NAME)&&this.char()){
            var reg=/[\w-\d]/;
            if(!reg.test(this.char())) return;
            let start=this.index;
            while(this.char()&&reg.test(this.char())){
                this.index++;
            }
            return {
                type:Types.TK_ATTR_NAME,
                label:this.input.slice(start,this.index)
            };
        }
        
    }
    private readAttrEqual():Token{
        if (this.inContext(Types.TK_TAG_NAME)&&this.char()==='=') {
            this.index++;
            return {
                type:Types.TK_ATTR_EQUAL,
                label:'='
            }
        }
    }
    private readAttrString():Token{
        if (this.inContext(Types.TK_TAG_NAME)&&/['"]/.test(this.char())) {
            let quote=this.char();
            let start=this.index;
            this.index++;
            while (!isUndefined(this.char())&&this.char()!==quote) {
                this.index++;
            }
            this.index++;
            return{
                type:Types.TK_ATTR_STRING,
                label:this.input.slice(start+1,this.index-1)
            }
        }
    }
    private readCloseTag():Token {
        return this.captureByRegx(
            /^\<\s*?\/\s*?[\w\d-]+?\s*?\>/,
            Types.TK_CLOSE_TAG
        );
    }
    private readGT():Token{
        if (this.char()==='>') {
            this.index++;
            this.setContext(Types.TK_GT);
            return{
                type:Types.TK_GT,
                label:">"
            }
        }
    }
    private readSlashGT():Token {
        return this.captureByRegx(
            /^\/\>/,
            Types.TK_SLASH_GT
        );
    }
    private readText():Token {
        if (!this.inContext(Types.TK_TAG_NAME)) {
            var start=this.index;
            if(!this.char()) return;
            this.index++;
            while(
                this.char()&&!(/[\<\{]/.test(this.char()))
            ){
                this.index++;
            }
            return {
                type:Types.TK_TEXT,
                label:this.input.slice(start,this.index)
            }
        }
    }

    private readIf():Token{
        return this.captureByRegx(
            /^\{\s*?if\s[\S\s]*?\}/,
            Types.TK_IF
        );
    }
    private readElse():Token {
        return this.captureByRegx(
            /^\{\s*else\s*\}/,
            Types.TK_ELSE
        );
    }
    private readElseIf():Token {
        return this.captureByRegx(
            /^\{\s*elseif\s*[\S\s]+?\}/,
            Types.TK_ELSE_IF
        );
    }
    private readEndIf():Token {
        return this.captureByRegx(
            /^\{\s*\/if\s*\}/,
            Types.TK_END_IF
        );
    }
    private readEach():Token {
        return this.captureByRegx(
            /^\{\s*each\s*[\S\s]*?\}/,
            Types.TK_EACH
        );
    }
    private readEndEach() {
        return this.captureByRegx(
            /^\{\s*\/each\s*\}/,
            Types.TK_END_EACH
        );
    }
    private readEOF():Token {
        if (this.index >= this.input.length) {
            this.eof = true;
            return {
                type: Types.TK_EOF,
                label: '$'
            }
        }
    }
}
